﻿using System;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Parsing;

namespace OpenLS.Spreadsheet
{
    abstract class CellFormulaBase
    {
        public static CellFormulaBase Verify (CellFormulaBase formula, Worksheet worksheet)
        {
            if (worksheet == null)
                return formula;
            
            if (formula is UnparsedFormula)
                return Parse(formula.Text, worksheet);
            return formula;

        }
        public static CellFormulaBase ParseFromConditionalFormatting(string formula, Worksheet worksheet)
        {
            if (string.IsNullOrEmpty(formula))
                return null;
            if (!(formula.Trim().StartsWith("=")))
                formula = "=" + formula;
            return Parse(formula, worksheet);
        }
        public static CellFormulaBase Parse(string formula, Worksheet worksheet)
        {
            if (string.IsNullOrEmpty(formula))
                return null;
            double dblResult;
            if (double.TryParse(formula, out dblResult))
                return new DoubleFormula(dblResult);
            if (formula.StartsWith("\"") & formula.EndsWith("\""))
                return new StringFormula(formula.Substring(1, formula.Length - 2));
            if (worksheet == null)
                return new UnparsedFormula(formula);
            var parser = new ExpressionParser(worksheet, null, LocalizationMode.NonLocalized, CellReferenceMode.Dynamic);
            var expression = parser.Expression(formula);
            return new WorksheetExpressionFormula(expression, worksheet);

        }
        public abstract string Text
        {
            get;
        }
        public virtual string TextWithoutEqual
        {
            get
            {
                Contract.Ensures(Contract.Result<string>() == null || !Contract.Result<string>().StartsWith("="));
                return Text;
            }
        }

        internal abstract object GetValue();


        public enum ComparisonResult
        {
            LessThan = -1,
            Equal = 0,
            GreaterThan = 1,
            NotEqual = -2,
            NotComparable = -3
        }

        static ComparisonResult Compare(object value1, object value2, Workbook workbook)
        {
            if (value1 == value2)
                return ComparisonResult.Equal;
            if (value1 == null && value2 == null)
                return ComparisonResult.Equal;
            if (value1 == null || value2 == null)
                return ComparisonResult.NotEqual;
            if (CellHelper.IsDoubleValue(value1))
            {
                double d1 = CellHelper.DoubleValue(value1);
                if (CellHelper.IsDoubleValue(value2))
                {
                    double d2 = CellHelper.DoubleValue(value2);
                    return (ComparisonResult) d1.CompareTo(d2);
                }
                if (value2 is DateTime)
                {
                    double dbl = workbook.DateTimeToDouble((DateTime) value2);
                    return (ComparisonResult) d1.CompareTo(dbl);
                }
            }
            if (value1 is DateTime)
            {
                DateTime d1 = (DateTime)(value1);
                if (CellHelper.IsDoubleValue(value2))
                {
                    double d2 = CellHelper.DoubleValue(value2);
                    return (ComparisonResult)d1.CompareTo(d2);
                }
                if (value2 is DateTime)
                {
                    double dbl = workbook.DateTimeToDouble((DateTime)value2);
                    return (ComparisonResult)d1.CompareTo(dbl);
                }
            }
            if (value1 is Error || value2 is Error)
                return ComparisonResult.NotComparable;
            if (value1 is bool && value2 is bool)
                return (bool)value1 == (bool)value2 ? ComparisonResult.Equal : ComparisonResult.NotEqual;
            if (value1 is string && value2 is string)
                return (ComparisonResult)((string)value1).CompareTo(value2);
            if (value1 is string && !(value2 is string))
                return ComparisonResult.NotComparable;
            if (!(value1 is string) && (value2 is string))
                return ComparisonResult.NotComparable;
            // Contract.Assert(false);
            return ComparisonResult.NotComparable;
        }

        public ComparisonResult CompareValueTo(object value, Workbook workbook)
        {
            return Compare(GetValue(), value, workbook);
        }

        public abstract CellFormulaBase Offset(CellRelativeAddress cellRelativeAddress);
    }
}